/**
 * 将 Lucene 查询语句转换成 EasySearch 可用的 ES Bool Query
 * 支持字段检索、AND/OR/NOT、括号以及隐式 AND
 */

const PRECEDENCE = {
    OR: 1,
    AND: 2,
    NOT: 3
};

const ASSOCIATIVITY = {
    OR: 'left',
    AND: 'left',
    NOT: 'right'
};

const BOOL_KEYS = ['must', 'filter', 'should', 'must_not'];
const DEFAULT_SEARCH_FIELDS = ['*'];

/**
 * 入口函数
 * @param {string} lucene Lucene 查询语句
 * @returns {{indexBase: string|null, query: object}}
 */
export function buildEasySearchQuery(lucene) {
    if (!lucene || !lucene.trim()) {
        throw new Error('请输入 Lucene 查询语句，例如 index:"buzz-service" AND reqId:"xxxxxx"');
    }
    const tokens = insertImplicitAnd(tokenize(lucene.trim()));
    const ast = parseToAst(tokens);
    const context = {
        indexBase: null
    };
    const query = ast ? astToEsQuery(ast, context) : null;
    return {
        indexBase: context.indexBase,
        query: ensureBoolQuery(query)
    };
}

/**
 * 对 Lucene 语句进行分词
 * @param {string} input
 * @returns {Array<object>}
 */
function tokenize(input) {
    const tokens = [];
    let i = 0;
    while (i < input.length) {
        const char = input[i];
        if (/\s/.test(char)) {
            i += 1;
            continue;
        }
        if (char === '(') {
            tokens.push({type: 'LPAREN'});
            i += 1;
            continue;
        }
        if (char === ')') {
            tokens.push({type: 'RPAREN'});
            i += 1;
            continue;
        }
        const op = readOperator(input, i);
        if (op) {
            tokens.push({type: 'OP', value: op});
            i += op.length;
            continue;
        }
        const term = readTerm(input, i);
        if (term) {
            tokens.push({type: 'TERM', field: term.field, value: term.value});
            i = term.nextIndex;
            continue;
        }
        const text = readText(input, i);
        if (!text) {
            throw new Error(`无法解析的语法片段: ${input.slice(i)}`);
        }
        tokens.push({type: 'TEXT', value: text.value});
        i = text.nextIndex;
    }
    return tokens;
}

/**
 * 读取逻辑运算符
 * @param {string} input
 * @param {number} index
 * @returns {string|null}
 */
function readOperator(input, index) {
    for (const op of ['AND', 'OR', 'NOT']) {
        if (input.startsWith(op, index) && isBoundary(input, index, op.length)) {
            return op;
        }
    }
    return null;
}

/**
 * 判断运算符是否与空白或括号分隔
 */
function isBoundary(input, index, length) {
    const startOk = index === 0 || /\s|\(/.test(input[index - 1]);
    const endIndex = index + length;
    const endOk = endIndex >= input.length || /\s|\)/.test(input[endIndex]);
    return startOk && endOk;
}

/**
 * 读取字段查询
 * @returns {{field: string, value: string, nextIndex: number}|null}
 */
function readTerm(input, startIndex) {
    let i = startIndex;
    const len = input.length;
    const fieldStart = i;
    while (i < len && ![':', ' ', '\t', '\n', '\r', '(', ')'].includes(input[i])) {
        i += 1;
    }
    if (i === fieldStart) {
        return null;
    }
    const field = input.slice(fieldStart, i);
    while (i < len && /\s/.test(input[i])) {
        i += 1;
    }
    if (i >= len || input[i] !== ':') {
        return null;
    }
    i += 1; // skip colon
    while (i < len && /\s/.test(input[i])) {
        i += 1;
    }
    if (i >= len) {
        return {field, value: '', nextIndex: i};
    }
    let value;
    if (input[i] === '"') {
        const {quotedValue, nextIndex} = readQuoted(input, i);
        value = quotedValue;
        i = nextIndex;
    } else {
        const valueStart = i;
        while (i < len && !/\s|\(|\)/.test(input[i])) {
            i += 1;
        }
        value = input.slice(valueStart, i);
    }
    return {field, value: unescapeValue(value), nextIndex: i};
}

/**
 * 读取双引号包裹的值
 */
function readQuoted(input, startIndex) {
    let i = startIndex + 1;
    let result = '';
    let escaped = false;
    while (i < input.length) {
        const ch = input[i];
        if (escaped) {
            result += ch;
            escaped = false;
        } else if (ch === '\\') {
            escaped = true;
        } else if (ch === '"') {
            return {quotedValue: result, nextIndex: i + 1};
        } else {
            result += ch;
        }
        i += 1;
    }
    throw new Error('缺少结束引号，请检查查询语法是否完整');
}

function unescapeValue(value) {
    return value.replace(/\\(["\\])/g, '$1');
}

function readText(input, startIndex) {
    const len = input.length;
    if (input[startIndex] === '"') {
        const {quotedValue, nextIndex} = readQuoted(input, startIndex);
        return {value: quotedValue.trim(), nextIndex};
    }
    let i = startIndex;
    while (i < len && !/\s|\(|\)/.test(input[i])) {
        i += 1;
    }
    const raw = input.slice(startIndex, i).trim();
    if (!raw) {
        return null;
    }
    return {value: raw, nextIndex: i};
}

/**
 * 插入隐式 AND
 */
function insertImplicitAnd(tokens) {
    if (tokens.length === 0) {
        return tokens;
    }
    const result = [];
    let prev = null;
    for (const token of tokens) {
        if (needsImplicitAnd(prev, token)) {
            result.push({type: 'OP', value: 'AND'});
        }
        result.push(token);
        prev = token;
    }
    return result;
}

function needsImplicitAnd(prev, current) {
    if (!prev) {
        return false;
    }
    const prevIsOperand = prev.type === 'TERM' || prev.type === 'TEXT' || prev.type === 'RPAREN';
    const currentStartsOperand = current.type === 'TERM' || current.type === 'TEXT' || current.type === 'LPAREN' || (current.type === 'OP' && current.value === 'NOT');
    return prevIsOperand && currentStartsOperand;
}

/**
 * 将 token 转成 AST（逆波兰表达式 + 栈规约）
 */
function parseToAst(tokens) {
    const output = [];
    const operators = [];
    for (const token of tokens) {
        switch (token.type) {
            case 'TERM':
            case 'TEXT':
                output.push(token);
                break;
            case 'OP': {
                while (operators.length > 0) {
                    const top = operators[operators.length - 1];
                    if (top.type !== 'OP') {
                        break;
                    }
                    const shouldPop = shouldPopOperator(token.value, top.value);
                    if (!shouldPop) {
                        break;
                    }
                    output.push(operators.pop());
                }
                operators.push(token);
                break;
            }
            case 'LPAREN':
                operators.push(token);
                break;
            case 'RPAREN': {
                let matched = false;
                while (operators.length > 0) {
                    const op = operators.pop();
                    if (op.type === 'LPAREN') {
                        matched = true;
                        break;
                    }
                    output.push(op);
                }
                if (!matched) {
                    throw new Error('括号不匹配，请检查查询语法');
                }
                break;
            }
            default:
                throw new Error(`无法识别的 token: ${JSON.stringify(token)}`);
        }
    }
    while (operators.length > 0) {
        const op = operators.pop();
        if (op.type === 'LPAREN' || op.type === 'RPAREN') {
            throw new Error('括号不匹配，请检查查询语法');
        }
        output.push(op);
    }
    return buildAstFromRpn(output);
}

function shouldPopOperator(current, top) {
    const currentPrec = PRECEDENCE[current] ?? 0;
    const topPrec = PRECEDENCE[top] ?? 0;
    if (currentPrec === 0 || topPrec === 0) {
        return false;
    }
    if (ASSOCIATIVITY[current] === 'right') {
        return currentPrec < topPrec;
    }
    return currentPrec <= topPrec;
}

function buildAstFromRpn(tokens) {
    const stack = [];
    for (const token of tokens) {
        if (token.type === 'TERM') {
            stack.push({type: 'TERM', field: token.field, value: token.value});
            continue;
        }
        if (token.type === 'TEXT') {
            stack.push({type: 'TEXT', value: token.value});
            continue;
        }
        if (token.type === 'OP') {
            if (token.value === 'NOT') {
                if (stack.length === 0) {
                    throw new Error('NOT 操作符缺少操作数');
                }
                const operand = stack.pop();
                stack.push({type: 'NOT', child: operand});
            } else {
                if (stack.length < 2) {
                    throw new Error(`${token.value} 操作符缺少操作数`);
                }
                const right = stack.pop();
                const left = stack.pop();
                stack.push({type: token.value, left, right});
            }
            continue;
        }
        throw new Error(`无法识别的语法节点: ${JSON.stringify(token)}`);
    }
    if (stack.length !== 1) {
        throw new Error('Lucene 语法解析失败，请检查是否缺失运算符');
    }
    return stack[0];
}

/**
 * AST -> ES Bool Query
 */
function astToEsQuery(node, context) {
    if (!node) {
        return null;
    }
    switch (node.type) {
        case 'TERM':
            return termToQuery(node, context);
        case 'TEXT':
            return textToQuery(node);
        case 'NOT': {
            const childQuery = astToEsQuery(node.child, context);
            if (!childQuery) {
                throw new Error('NOT 子句缺少有效的查询条件');
            }
            return {
                bool: {
                    must_not: [childQuery]
                }
            };
        }
        case 'AND': {
            const left = astToEsQuery(node.left, context);
            const right = astToEsQuery(node.right, context);
            return mergeBoolQueries('filter', left, right);
        }
        case 'OR': {
            const left = astToEsQuery(node.left, context);
            const right = astToEsQuery(node.right, context);
            return mergeBoolQueries('should', left, right, {minimumShouldMatch: 1});
        }
        default:
            throw new Error(`暂不支持的 AST 节点类型: ${node.type}`);
    }
}

function termToQuery(node, context) {
    const field = node.field.trim();
    const value = node.value.trim();
    if (!field) {
        throw new Error('存在缺失字段名的查询条件，请检查语法');
    }
    if (field.toLowerCase() === 'index') {
        context.indexBase = value;
        return null;
    }
    if (!value) {
        return {
            bool: {
                must_not: [{
                    exists: {
                        field
                    }
                }]
            }
        };
    }
    if (value.includes('*') || value.includes('?')) {
        return {
            wildcard: {
                [field]: {
                    value
                }
            }
        };
    }
    if (value === 'null') {
        return {
            bool: {
                must_not: [{
                    exists: {field}
                }]
            }
        };
    }
    return {
        match_phrase: {
            [field]: value
        }
    };
}

function textToQuery(node) {
    const value = (node.value ?? '').trim();
    if (!value) {
        throw new Error('存在空的查询关键字，请确认输入是否正确');
    }
    return {
        multi_match: {
            type: 'phrase',
            query: value,
            lenient: true,
            fields: DEFAULT_SEARCH_FIELDS
        }
    };
}

function mergeBoolQueries(targetKey, left, right, options = {}) {
    const fragments = [left, right].filter(Boolean);
    if (fragments.length === 0) {
        return null;
    }
    if (fragments.length === 1) {
        return wrapSingleFragment(fragments[0], targetKey, options);
    }
    const bool = createEmptyBool();
    let minimumShouldMatch = options.minimumShouldMatch ?? 0;
    for (const fragment of fragments) {
        if (fragment.bool) {
            for (const key of BOOL_KEYS) {
                if (Array.isArray(fragment.bool[key]) && fragment.bool[key].length > 0) {
                    bool[key].push(...fragment.bool[key]);
                }
            }
            if (fragment.bool.minimum_should_match) {
                minimumShouldMatch = Math.max(minimumShouldMatch, fragment.bool.minimum_should_match);
            }
        } else {
            bool[targetKey].push(fragment);
        }
    }
    if (targetKey === 'should' && bool.should.length > 0) {
        minimumShouldMatch = Math.max(minimumShouldMatch, options.minimumShouldMatch ?? 1, 1);
    }
    const cleaned = compactBoolClause(bool);
    if (targetKey === 'should') {
        cleaned.minimum_should_match = minimumShouldMatch || 1;
    } else if (minimumShouldMatch > 0) {
        cleaned.minimum_should_match = minimumShouldMatch;
    }
    return {bool: cleaned};
}

function wrapSingleFragment(fragment, targetKey, options) {
    if (!fragment) {
        return null;
    }
    if (fragment.bool) {
        const cloned = cloneBoolClause(fragment.bool);
        if (targetKey === 'should') {
            const expected = Math.max(options.minimumShouldMatch ?? 0, cloned.minimum_should_match ?? 0);
            if (cloned.should && cloned.should.length > 0) {
                cloned.minimum_should_match = Math.max(expected, 1);
            } else if (expected > 0) {
                cloned.minimum_should_match = expected;
            }
        }
        return {bool: cloned};
    }
    if (targetKey === 'should') {
        return {
            bool: {
                should: [fragment],
                minimum_should_match: options.minimumShouldMatch || 1
            }
        };
    }
    return {
        bool: {
            [targetKey]: [fragment]
        }
    };
}

function createEmptyBool() {
    return {
        must: [],
        filter: [],
        should: [],
        must_not: []
    };
}

function cloneBoolClause(boolClause) {
    const cloned = createEmptyBool();
    for (const key of BOOL_KEYS) {
        if (Array.isArray(boolClause[key]) && boolClause[key].length > 0) {
            cloned[key].push(...boolClause[key]);
        }
    }
    if (boolClause.minimum_should_match) {
        cloned.minimum_should_match = boolClause.minimum_should_match;
    }
    return cloned;
}

function compactBoolClause(boolClause) {
    const cleaned = {};
    for (const key of BOOL_KEYS) {
        if (boolClause[key].length > 0) {
            cleaned[key] = boolClause[key];
        }
    }
    if (boolClause.minimum_should_match) {
        cleaned.minimum_should_match = boolClause.minimum_should_match;
    }
    return cleaned;
}

function ensureBoolQuery(query) {
    if (!query) {
        return {match_all: {}};
    }
    if (query.bool) {
        const cloned = cloneBoolClause(query.bool);
        const cleaned = compactBoolClause(cloned);
        if (Object.keys(cleaned).length === 0) {
            return {match_all: {}};
        }
        return {bool: cleaned};
    }
    return query;
}
